\epi{``好的沟通就像是一杯刺激的浓咖啡，然后就难以入睡。''}{\textsc{ANNE MORROW LINDBERGH}}
\noindent{}在这章中将介绍 Go 中与外部通讯的通讯模块。将会了解文件、目录、网络通讯和运行其他程序。Go 的 I/O 核心是接口 \type{io.Reader} 和 \type{io.Writer}。

在 Go 中，从文件读取（或写入）是非常容易的。程序只需要使用
\package{os} 包就可以从文件 \file{/etc/passwd} 中读取数据。
\lstinputlisting[caption=从文件读取（无缓冲）,label=src:read]{src/file.go}
接下来展示了如何做到这点：
\showremarks
如果想要使用\first{缓冲}{buffered} IO，则有
\package{bufio}\index{package!bufio} 包：
\lstinputlisting[caption=从文件读取（缓冲）,label=src:bufread]{src/buffile.go}
\showremarks

\section{io.Reader}
在前面已经提到 \first{io.Reader}{io.Reader} 接口对于 Go 语言来说非常重要。许多（如果不是全部的话）函数需要通过 
\type{io.Reader}\index{package!io} 读取一些数据作为输入。
为了满足这个接口，只需要实现一个方法：
\func{Read(p []byte) (n int, err error)}。
写入则是（你可能已经猜到了）实现了 \func{Write} 方法的 \type{io.Writer}。
如果你让自己的程序或者包中的类型实现了 \type{io.Reader} 或者
\type{io.Writer} 接口，那么\emph{整个 Go 标准库都可以使用}这个类型！

\section{一些例子}

前面的程序将整个文件读出，但是通常情况下会希望一行一行的读取。下面的片段展示了如何实现：

\begin{lstlisting}
f, _ := os.Open("/etc/passwd"); defer f.Close()
r := bufio.NewReader(f) |\coderemark{使其成为一个 bufio，以便访问 ReadString 方法}|
s, ok := r.ReadString('\n') {|\coderemark{从输入中读取一行}|
// ... |\coderemark{\var{s} 保存了字符串，通过 \package{string} 包就可以解析它}|
\end{lstlisting}

更加通用（但是也更加复杂）的方法是~\func{ReadLine}，参阅包
~\package{bufio} 的文档了解更多内容。

在~shell 脚本中通常遇到的场景是需要检查某个目录是否存在，
如果不存在，就创建一个。

\begin{minipage}{.5\textwidth}
\begin{lstlisting}[language=sh,caption={用~shell 创建一个目录}]
if [ ! -e name ]; then
    mkdir name
else
    # error
fi
\end{lstlisting}
\end{minipage}
\hspace{1em}
\begin{minipage}{.5\textwidth}
\begin{lstlisting}[caption={用~Go 创建一个目录}]
if f, e := os.Stat("name"); e != nil {
    os.Mkdir("name", 0755)
} else {
    // error
}
\end{lstlisting}
\end{minipage}
这两个例子的相似之处展示了 Go 拥有的``脚本''化特性，例如，用 Go  编写程序感觉上类似使用动态语言（Python、Ruby、Perl 或者 PHP）。

\section{命令行参数}
\label{sec:option parsing}
来自命令行的参数在程序中通过字符串 slice \var{os.Args} 获取，导入包 \package{os} 即可。
\package{flag} 包有着精巧的接口，同样提供了解析标识的方法。这个例子是一个 DNS 查询工具：
\begin{lstlisting}
dnssec := flag.Bool("dnssec", false, "Request DNSSEC records") |\longremark{定义 \texttt{bool} 标识，%%
\texttt{-dnssec}。变量必须是指针，否则 package 无法设置其值；}|
port := flag.String("port", "53", "Set the query port")      |\longremark{类似的，\texttt{port} 选项；}|
flag.Usage = func() {   |\longremark{简单的重定义 \func{Usage} 函数，有点罗嗦；}|
    fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] [name ...]\n", os.Args[0])
    flag.PrintDefaults() |\longremark{指定的每个标识，\func{PrintDefaults} 将输出帮助信息；}|
}
flag.Parse()   |\longremark{解析标识，并填充变量。}|
\end{lstlisting}
\showremarks
当参数被解析之后，就可以使用它们：
\begin{lstlisting}
if *dnssec {    |\coderemark{定义传入参数 \var{dnssec}}|
    // 做点啥
}
\end{lstlisting}

\section{执行命令}
\package{os/exec}\index{package!os/exec} 包有函数可以执行外部命令，这也是在 Go 中主要的执行命令的方法。
通过定义一个有着数个方法的 \var{*exec.Cmd} 结构来使用。

执行 \verb|ls -l|：
\begin{lstlisting}
import "os/exec"

cmd := exec.Command("/bin/ls", "-l")
err := cmd.Run()
\end{lstlisting}
上面的例子运行了``ls -l''，但是没有对其返回的数据进行任何处理，
通过如下方法从命令行的标准输出中获得信息：
\begin{lstlisting}
import "exec"

cmd := exec.Command("/bin/ls", "-l")
buf, err := cmd.Output()    |\coderemark{\var{buf} 是一个 \type{[]byte}}|
\end{lstlisting}

\section{网络}
所有网络相关的类型和函数可以在 \package{net} 包中找到。这其中最重要的函数是 \func{Dial}\index{networking!Dial}。
当 \func{Dial} 到远程系统，这个函数返回 \var{Conn} 接口类型，可以用于发送或接收信息。
函数 \func{Dial} 简洁的抽象了网络层和传输层。因此 IPv4 或者 IPv6，TCP 或者 UDP 可以共用一个接口。

通过 TCP 连接到远程系统（端口 80），然后是 UDP，最后是 TCP 通过 IPv6，大致是这样
\footnote{在这个例子中，可以认为 192.0.32.10 和 2620:0:2d0:200::10 是 \url{www.example.org}。}：
\begin{lstlisting}
conn, e := Dial("tcp", "192.0.32.10:80")
conn, e := Dial("udp", "192.0.32.10:80")
conn, e := Dial("tcp", "[2620:0:2d0:200::10]:80") |\coderemark{方括号是强制的}|
\end{lstlisting}

如果没有错误（由 \var{e} 返回），就可以使用 \var{conn} 从套接字中读写。
在包 \package{net} 中的原始定义是：
\begin{quote}
// \func{Read} reads data from the connection.\\
\lstinline{Read(b []byte) (n int, err error)}
\end{quote}
这使得 \var{conn} 成为了 \type{io.Reader}。

\begin{quote}
// \func{Write} writes data to the connection.\\
\lstinline{Write(b []byte) (n int, err error)}
\end{quote}
这同样使得 \var{conn} 成为了 \type{io.Writer}，事实上 \var{conn} 是 \type{io.ReadWriter}。\footnote{变量 \var{conn} 同样实现了 \func{close} 方法，这使其成为一个 \type{io.ReadWriteCloser}。}

但是这些都是隐含的低层\footnote{练习 Q\ref{ex:echo} 是关于使用这些的。}，通常总是应该使用更高层次的包。
例如 \package{http} 包。一个简单的 http Get 作为例子：
\begin{lstlisting}
package main
import ( "io/ioutil"; "net/http"; "fmt" ) |\longremark{需要的导入；}|

func main() {
    r, err := http.Get("http://www.google.com/robots.txt") |\longremark{使用 http 的 \func{Get} 获取 html；}|
    if err != nil { fmt.Printf("%s\n", err.Error()); return } |\longremark{错误处理；}|
    b, err := ioutil.ReadAll(r.Body)    |\longremark{将整个内容读入 \var{b}；}|
    r.Body.Close()  
    if err == nil { fmt.Printf("%s", string(b)) } |\longremark{如果一切 OK 的话，打印内容。}|
}
\end{lstlisting}
\showremarks

\section{练习}
\input{ex-communication/ex-processes.tex}

\input{ex-communication/ex-wordcount.tex}

\input{ex-communication/ex-uniq.tex}

\input{ex-communication/ex-quine.tex}

\input{ex-communication/ex-echo.tex}

\input{ex-communication/ex-numbercruncher.tex}

\input{ex-communication/ex-finger.tex}

\cleardoublepage
\section{答案}
\shipoutAnswer
